/**
 * 
 */
package com.miolr.module.core.social;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import com.miolr.module.core.model.SocialConnection;
import com.miolr.module.core.service.SocialConnectionService;

/**
 * @author iday
 * 
 */
public class SocialUserConnectionRepository implements ConnectionRepository {

	private final SocialConnectionService connectionService;

	private final String userId;

	private final ConnectionFactoryLocator connectionFactoryLocator;

	private final TextEncryptor textEncryptor;

	public SocialUserConnectionRepository(SocialConnectionService connectionService,
			String userId, ConnectionFactoryLocator connectionFactoryLocator,
			TextEncryptor textEncryptor) {
		super();
		this.connectionService = connectionService;
		this.userId = userId;
		this.connectionFactoryLocator = connectionFactoryLocator;
		this.textEncryptor = textEncryptor;
	}

	@Override
	public void addConnection(Connection<?> connection) {
		connectionService.save(new SocialConnection(userId, connection.createData()));
	}

	@Override
	public MultiValueMap<String, Connection<?>> findAllConnections() {
		List<SocialConnection> list = connectionService.findByUserId(userId);
		MultiValueMap<String, Connection<?>> connections = new LinkedMultiValueMap<String, Connection<?>>();
		Set<String> registeredProviderIds = connectionFactoryLocator
				.registeredProviderIds();
		for (String registeredProviderId : registeredProviderIds) {
			connections.put(registeredProviderId,
					Collections.<Connection<?>> emptyList());
		}
		for (SocialConnection conn : list) {
			Connection<?> connection = getConnection(conn);
			String providerId = connection.getKey().getProviderId();
			if (connections.get(providerId).size() == 0) {
				connections.put(providerId, new LinkedList<Connection<?>>());
			}
			connections.add(providerId, connection);
		}
		return connections;
	}

	@Override
	public List<Connection<?>> findConnections(String providerId) {
		List<SocialConnection> list = connectionService.findByUserId(
				providerId, userId);
		List<Connection<?>> connections = new ArrayList<Connection<?>>();
		for (SocialConnection conn : list) {
			connections.add(getConnection(conn));
		}
		return connections;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <A> List<Connection<A>> findConnections(Class<A> apiType) {
		List<?> connections = findConnections(getProviderId(apiType));
		return (List<Connection<A>>) connections;
	}

	@Override
	public MultiValueMap<String, Connection<?>> findConnectionsToUsers(
			MultiValueMap<String, String> providerUsers) {
		List<SocialConnection> list = connectionService.findByProviderUsers(
				userId, providerUsers);
		MultiValueMap<String, Connection<?>> connectionsForUsers = new LinkedMultiValueMap<String, Connection<?>>();
		for (SocialConnection conn : list) {
			Connection<?> connection = getConnection(conn);
			String providerId = connection.getKey().getProviderId();
			List<String> userIds = providerUsers.get(providerId);
			List<Connection<?>> connections = connectionsForUsers
					.get(providerId);
			if (connections == null) {
				connections = new ArrayList<Connection<?>>(userIds.size());
				for (int i = 0; i < userIds.size(); i++) {
					connections.add(null);
				}
				connectionsForUsers.put(providerId, connections);
			}
			String providerUserId = connection.getKey().getProviderUserId();
			int connectionIndex = userIds.indexOf(providerUserId);
			connections.set(connectionIndex, connection);
		}
		return connectionsForUsers;
	}

	@Override
	public <A> Connection<A> findPrimaryConnection(Class<A> arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Connection<?> getConnection(ConnectionKey connectionKey) {
		SocialConnection conn = connectionService.findOne(
				connectionKey.getProviderId(),
				connectionKey.getProviderUserId(), userId);
		return getConnection(conn);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <A> Connection<A> getConnection(Class<A> apiType,
			String providerUserId) {
		SocialConnection conn = connectionService.findOne(
				getProviderId(apiType), providerUserId, userId);
		return (Connection<A>) getConnection(conn);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <A> Connection<A> getPrimaryConnection(Class<A> apiType) {
		SocialConnection conn = connectionService.findPrimary(
				getProviderId(apiType), userId);
		return (Connection<A>) getConnection(conn);
	}

	@Override
	public void removeConnection(ConnectionKey connectionKey) {
		connectionService.delete(connectionService.findOne(
				connectionKey.getProviderId(),
				connectionKey.getProviderUserId(), userId));
	}

	@Override
	public void removeConnections(String providerId) {
		connectionService.deleteAll(connectionService.findByUserId(providerId,
				userId));
	}

	@Override
	public void updateConnection(Connection<?> connection) {
		ConnectionData data = connection.createData();
		SocialConnection conn = this.connectionService.findOne(
				data.getProviderId(), data.getProviderUserId(), userId);
		BeanUtils.copyProperties(data, conn, new String[] { "accessToken",
				"secret", "refreshToken", "expireTime" });
		conn.setAccessToken(encrypt(data.getAccessToken()));
		conn.setSecret(encrypt(data.getSecret()));
		conn.setRefreshToken(encrypt(data.getRefreshToken()));
		if (data.getExpireTime() != null) { 
			conn.setExpireTime(new Date(data.getExpireTime()));
		}
		this.connectionService.update(conn);
	}

	private <A> String getProviderId(Class<A> apiType) {
		return connectionFactoryLocator.getConnectionFactory(apiType)
				.getProviderId();
	}

	private String encrypt(String text) {
		return text != null ? textEncryptor.encrypt(text) : text;
	}

	/**
	 * @param conn
	 * @return
	 */
	private Connection<?> getConnection(SocialConnection conn) {
		ConnectionData data = conn.getData(textEncryptor);
		ConnectionFactory<?> connectionFactory = connectionFactoryLocator
				.getConnectionFactory(data.getProviderId());
		Connection<?> connection = connectionFactory.createConnection(data);
		return connection;
	}

}
